package example.simple_mindmap.chache;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import example.simple_mindmap.api.client.UserClient;
import example.simple_mindmap.common.config.RedisProperties;
import example.simple_mindmap.common.designs.theadPool.BulkExecuteHandler;
import example.simple_mindmap.common.designs.theadPool.BulkExecuteUtil;
import example.simple_mindmap.common.designs.theadPool.BulkTask;
import example.simple_mindmap.common.designs.theadPool.BulkTaskCollection;
import example.simple_mindmap.common.designs.theadPool.strategy.error.RetryAndCollectError;
import example.simple_mindmap.common.domain.dto.UserDTO;
import example.simple_mindmap.common.domain.po.Result;
import example.simple_mindmap.common.enums.ResultCode;
import example.simple_mindmap.common.utils.DataUtil;
import example.simple_mindmap.common.utils.SessionHolder;
import example.simple_mindmap.common.utils.cacheUtil.CacheClient;
import example.simple_mindmap.config.MindMapProperties;
import example.simple_mindmap.domain.dto.MindMapDTO;
import example.simple_mindmap.domain.dto.MindMapListDTO;
import example.simple_mindmap.domain.po.MindMap;
import example.simple_mindmap.service.MindMapServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @BelongsProject: 3.mindMap_ai
 * @BelongsPackage: com.unfbx.utils
 * @Author : 游在海里的鱼
 * @CreateTime : 2024-05-01  14:36
 * @Version : 1.0
 * @ClassName : TODO
 * @Description : 导图的缓存
 */

@Slf4j
@Component
@RequiredArgsConstructor
public class MindMapCache  {
    private final StringRedisTemplate stringRedisTemplate;
    private final CacheClient cacheClient;
    private final UserClient userClient;

    private final MindMapServiceImpl mindMapService;

    private final RedisProperties redisProperties;
    private final MindMapProperties mmProperties;

    private static final Long CACHE_NULL_TLL_MINUTE = 2L;
    private static final Long CACHE_EXPIRE_RANDOM_SECOND = 60L;




    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 删除在Zset中的某个导图
     * @Describe: TODO
     **/
    @Transactional
     public Boolean removeMindMap(Long id){
        String idStr = String.valueOf(id);
        String ZsetKey1 = redisProperties.cache_mindmap_index +"Zset";
        String ZsetKey2 = redisProperties.cache_mindmap_list +"public:Zset:"+ SessionHolder.getUser();
        String ZsetKey3 = redisProperties.cache_mindmap_list +"Zset:"+SessionHolder.getUser();
         //删除Zset
         return !DataUtil.isNaturalNumInvalid(stringRedisTemplate.opsForZSet().remove(ZsetKey1, idStr))
        && !DataUtil.isNaturalNumInvalid(stringRedisTemplate.opsForZSet().remove(ZsetKey2, idStr))
        && !DataUtil.isNaturalNumInvalid(stringRedisTemplate.opsForZSet().remove(ZsetKey3, idStr))
        //删除List缓存
        && stringRedisTemplate.delete(redisProperties.cache_mindmap_list+idStr)
        //删除MindMap缓存
        && stringRedisTemplate.delete(redisProperties.cache_mindmap+idStr);

    }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 往Zset中添加新的导图
     * @Describe: TODO
     **/
    @Transactional
     public Boolean addOrUpdateMindMap1(MindMap mindMap){
        addOrUpdateMindMap2(mindMap);
        //三个Zset的Key，还有导图List的Key，还有导图的Key
        String ZsetKey1 = redisProperties.cache_mindmap_index + "Zset";
        //修改Zset
        if(stringRedisTemplate.hasKey(ZsetKey1)){
            stringRedisTemplate.opsForZSet().add(ZsetKey1, String.valueOf(mindMap.getId()), scoring1(mindMap));
        }
        return true;
    }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 往Zset中添加新的导图
     * @Describe: TODO
     **/
    @Transactional
    public Boolean addOrUpdateMindMap2(MindMap mindMap){
        addOrUpdate(mindMap);
        //三个Zset的Key，还有导图List的Key，还有导图的Key
        String ZsetKey2 = redisProperties.cache_mindmap_list + "public:Zset:"+ SessionHolder.getUser();
        String ZsetKey3 = redisProperties.cache_mindmap_list + "Zset:"+SessionHolder.getUser();

        //修改Zset
        if(stringRedisTemplate.hasKey(ZsetKey2)){
            stringRedisTemplate.opsForZSet().add(ZsetKey2, String.valueOf(mindMap.getId()), scoring2(mindMap));
        }
        if(stringRedisTemplate.hasKey(ZsetKey3)){
            stringRedisTemplate.opsForZSet().add(ZsetKey3, String.valueOf(mindMap.getId()), scoring2(mindMap));
        }
        return true;
    }

    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 修改或新增MindMap
     * @Describe: TODO
     **/
     private void addOrUpdate(MindMap mindMap){
        String id = String.valueOf(mindMap.getId());
        String mindListKey = redisProperties.cache_mindmap_list + id;
        String mindKey = redisProperties.cache_mindmap + id;

        //补全需要的数据
        MindMapDTO mindMapDTO = BeanUtil.toBean(mindMap, MindMapDTO.class);
        MindMapListDTO mindMapListDTO = BeanUtil.toBean(mindMap, MindMapListDTO.class);

        UserDTO userDTO = cacheClient.queryPassThrough(
                redisProperties.cache_user_pre+mindMap.getUid(),
                mindMap.getUid(),UserDTO.class
                ,redisProperties.session_user_save_time,TimeUnit.MINUTES
                ,this::getUser);
        if(userDTO == null){
            mindMapDTO.setAvatar(mmProperties.default_mindmap_avatar);
            mindMapDTO.setName(mmProperties.default_mindmap_author);
            mindMapListDTO.setAvatar(mmProperties.default_mindmap_avatar);
        }else {
            mindMapDTO.setAvatar(userDTO.getAvatarUrl());
            mindMapDTO.setName(userDTO.getNickname());
            mindMapListDTO.setAvatar(userDTO.getAvatarUrl());
        }
        //修改MindMap缓存
        stringRedisTemplate.opsForValue().set(mindKey,JSONUtil.toJsonStr(mindMapDTO)
                ,redisProperties.cache_mindmap_time,TimeUnit.MINUTES);
        //修改List缓存
        stringRedisTemplate.opsForValue().set(mindListKey,JSONUtil.toJsonStr(mindMapListDTO)
                ,redisProperties.cache_mindmap_list_time,TimeUnit.MINUTES);
    }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 获取导图
     * @Describe: TODO
     **/
     public MindMapDTO getMindMap(Long mid){
         String key = redisProperties.cache_mindmap+mid;
         MindMapDTO mindMap = cacheClient.queryPassThrough(key,mid,MindMapDTO.class
                 ,redisProperties.cache_mindmap_time,TimeUnit.MINUTES,this::loadMindMap);
         return mindMap;
    }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 获取导图列表的总页数
     * @Describe: TODO
     **/
     public Integer getPageCount(String ZsetKey){
         Integer count = stringRedisTemplate.opsForZSet().size(ZsetKey).intValue();
         return (count / mmProperties.page_capacity + (count % mmProperties.page_capacity >0 ?1 :0));
     }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 获取导图列表，从Zset中
     * @Describe: TODO
     **/
     public List<MindMapListDTO> getListByZset(String ZsetKey, int start, int end
             , Long time){

        //1.获取范围内的导图，从redis的Zset中
        List<String> mindMapIds = getRangeByZset(ZsetKey, start, end,time);

        //keySet为空,缓存空值
        if(mindMapIds == null || mindMapIds.isEmpty()){
            stringRedisTemplate.opsForZSet().add(ZsetKey, "null", 0.0);
            stringRedisTemplate.expire(ZsetKey,CACHE_NULL_TLL_MINUTE,TimeUnit.MINUTES);
            return null;
        }

        //keySet不为空，根据key从Redis获取导图

         //批量从redis中获取导图List，根据加前缀后的key
         List<String> mindMapKeys = new ArrayList<>();
         for (String mindMapId : mindMapIds) {
             mindMapKeys.add(redisProperties.cache_mindmap_list+mindMapId);
         }
         List<String> mindMaps = stringRedisTemplate.opsForValue().multiGet(mindMapKeys);

         //检查List是否有空值，也就是说redis中没有缓存的导图List
         List<Long> isNullMindMapId = new ArrayList<>();

         for(int i=0; i<mindMapIds.size(); i++){
             Long id = Long.valueOf(mindMapIds.get(i));
             if(mindMaps.get(i) == null){
                 isNullMindMapId.add(id);
             }
         }

         //有空值，则从数据库中批量获取空值对应的mindMap
         if(!isNullMindMapId.isEmpty()){
             List<MindMap> mindMapList = mindMapService
                     .lambdaQuery()
                     .select(MindMap::getId,MindMap::getTitle,MindMap::getUid,MindMap::getImage,MindMap::getLike,MindMap::getCollect,MindMap::getView,MindMap::getIsTop,MindMap::getCreateTime,MindMap::getUpdateTime)
                     .in(MindMap::getId, isNullMindMapId)
                     .list();
             List<MindMapListDTO> mindMapListDTOS = BeanUtil.copyToList(mindMapList, MindMapListDTO.class);
             loadUserInfo(mindMapListDTOS);

             //批量缓存，补全上面的空值
             for(MindMapListDTO mindMapListDTO: mindMapListDTOS){
                 stringRedisTemplate.opsForValue().set(
                         redisProperties.cache_mindmap_list+mindMapListDTO.getId()
                         , JSONUtil.toJsonStr(mindMapListDTO)
                         //加上随机值，避免雪崩
                         ,redisProperties.cache_mindmap_list_time*60 + RandomUtil.randomLong(CACHE_EXPIRE_RANDOM_SECOND)
                         ,TimeUnit.SECONDS);
             }
         }

         //根据mindMapId从redis读取导图List
         List<MindMapListDTO> ret = new ArrayList<>();
         for(String str: stringRedisTemplate.opsForValue().multiGet(mindMapKeys)){
             if(StrUtil.isNotBlank(str)){
                 ret.add(JSONUtil.toBean(str, MindMapListDTO.class));
             }
         }
        return ret;
    }


    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 获取范围内的导图，从redis的Zset中
     * @Describe: 因为导图的Zset属于实时排名，可以不设置过期时期
     **/
     private List<String> getRangeByZset(String zsetKey, int start, int end, Long time) {
         //获取置顶范围的Set，从Redis的Zset中
         Set<String> mindMapIds = stringRedisTemplate.opsForZSet().reverseRange(zsetKey, start, end);
         if (mindMapIds != null && !mindMapIds.isEmpty()) {
             return new ArrayList<>(mindMapIds);
         }

         //根据zsetKey来判断要导入哪些数据到redis中
         String[] strings = zsetKey.split(":");
         System.out.println(strings);
         if(strings[strings.length-1].equals("Zset")){
             importALl(zsetKey);
         }else if(strings[strings.length-2].equals("public")){
             String uid = strings[strings.length-1];
             importPublicOfUser(zsetKey,uid,time);
         }else if(strings[strings.length-2].equals("Zset")){
             String uid = strings[strings.length-1];
             importAllOfUser(zsetKey,uid,time);
         }

         //从zset中获取ID，排序过后的MindMapId
         mindMapIds = stringRedisTemplate.opsForZSet().reverseRange(zsetKey, start, end);
         return new ArrayList<>(mindMapIds);
     }

    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 导入某个用户的公开导图来排名
     * @Describe: TODO
     **/
     private void importPublicOfUser(String zsetKey,String uid,Long time){
        List<MindMap> mindMapList = mindMapService
                .lambdaQuery()
                .select(MindMap::getId,MindMap::getLike,MindMap::getCollect,MindMap::getView,MindMap::getIsTop,MindMap::getUpdateTime)
                .eq(MindMap::getState,1)
                .eq(MindMap::getUid,uid)
                .list();
        //添入Zset，将数据库数据添入
        for (MindMap mindMap : mindMapList) {
            String key = String.valueOf(mindMap.getId());
            stringRedisTemplate.opsForZSet().add(zsetKey, key, scoring2(mindMap));
        }
        stringRedisTemplate.expire(zsetKey, time, TimeUnit.MINUTES);
    }

    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 导入某个用户的所有导图来排名
     * @Describe: TODO
     **/
    private void importAllOfUser(String zsetKey,String uid,Long time){
        List<MindMap> mindMapList = mindMapService
                .lambdaQuery()
                .select(MindMap::getId,MindMap::getLike,MindMap::getCollect,MindMap::getView,MindMap::getIsTop,MindMap::getUpdateTime)
                .eq(MindMap::getUid,uid)
                .list();
        //添入Zset，将数据库数据添入
        for (MindMap mindMap : mindMapList) {
            String key = String.valueOf(mindMap.getId());
            stringRedisTemplate.opsForZSet().add(zsetKey, key, scoring2(mindMap));
        }
        stringRedisTemplate.expire(zsetKey, time, TimeUnit.MINUTES);
    }

    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 导入所有导图来进行排名
     * @Describe: TODO
     **/
    @SneakyThrows
    private void importALl(String zsetKey){
        long mindMapCount = Long.valueOf(mindMapService.lambdaQuery().count());
        int batch_size = mmProperties.batch_size;
        int totalPage = (int) (mindMapCount % batch_size == 0 ? mindMapCount / batch_size : mindMapCount / batch_size + 1);

        BulkTaskCollection taskList = BulkExecuteUtil.getTaskCollection(Task.class);
        BulkExecuteHandler handler = BulkExecuteUtil.getHandler(taskList);
        handler.setErrorStrategy(new RetryAndCollectError());

        //线程池批量导入
        //每批的数量
        for(int i =1; i<=totalPage; i++) {
            Task task = new Task(i,batch_size,zsetKey);
            task.setName("导入页："+i);
            //新增文档
            taskList .add(task);
        }
        handler.executeTasks();
        System.out.println(handler.getExecuteInfo());
    }

    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 任务类
     * @Describe: TODO
     **/
    class Task extends BulkTask {
        private int page;
        private int page_size;
        private String zsetKey;


        public Task(int page, int page_size, String zsetKey) {
            this.page = page;
            this.page_size = page_size;
            this.zsetKey = zsetKey;
        }

        @Override
        public boolean executeFunction()  {
            //获取指定页的导图
            Page<MindMap> pageSQL = new Page<>(page, page_size);
            List<MindMap> mindMapList = mindMapService
                    .lambdaQuery()
                    .select(MindMap::getId,MindMap::getLike,MindMap::getCollect,MindMap::getView,MindMap::getIsTop,MindMap::getUpdateTime)
                    .eq(MindMap::getState,1)
                    .page(pageSQL).getRecords();

            //批量导入
            //添入Zset，将数据库数据添入
            for (MindMap mindMap : mindMapList) {
                String key = String.valueOf(mindMap.getId());
                stringRedisTemplate.opsForZSet().add(zsetKey, key, scoring1(mindMap));
            }
            return true;
        }
    }

    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 计算分数
     * @Describe: TODO
     **/
    private Double scoring1(MindMap mindMap){
        return mindMap.getLike() * mmProperties.like_score
                + mindMap.getCollect() * mmProperties.collect_score
                + mindMap.getView() * mmProperties.view_score;
    }

    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 计算分数
     * @Describe: TODO
     **/
    private Double scoring2(MindMap mindMap){
        return (mindMap.getIsTop()==1? new Date().getTime():0)*1.0+mindMap.getUpdateTime().getTime();
    }

    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 加载每个导图的用户信息
     * @Describe: TODO
     **/
    private List<MindMapListDTO> loadUserInfo(List<MindMapListDTO> mindMapListDTO){
        //加载每个导图列表的用户信息
        for(MindMapListDTO mindMapDTO : mindMapListDTO){
            UserDTO userDTO = cacheClient.queryPassThrough(
                    redisProperties.cache_user_pre+mindMapDTO.getUid()
                    , mindMapDTO.getUid(),UserDTO.class
                    ,redisProperties.session_user_save_time,TimeUnit.MINUTES
                    ,this::getUser);
            if(userDTO == null){
                mindMapDTO.setAvatar(mmProperties.default_mindmap_avatar);
            }else {
                mindMapDTO.setAvatar(userDTO.getAvatarUrl());
            }
        }
        return mindMapListDTO;
    }


    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 获取用户信息
     * @Describe: TODO
     **/
    private UserDTO getUser(Long uid){
        UserDTO userDTO = null;
        try {
            Result result = userClient.userInfo(uid);
            if(ResultCode.SUCCESS.getCode() != result.getCode()){
                log.warn("获取用户信息失败");
                return null;
            }
            userDTO = (UserDTO) result.getData();
        } catch (Exception e) {
            log.warn("获取用户信息失败");
            return null;
        }
        return userDTO;
    }

    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 加载mid导图
     * @Describe: TODO
     **/
    private MindMapDTO loadMindMap(Long mid){
        MindMap mindMap = mindMapService.getById(mid);
        if(mindMap == null){
            return null;
        }
        MindMapDTO mindMapDTO = BeanUtil.copyProperties(mindMap, MindMapDTO.class);


        //加载用户信息
        UserDTO userDTO = cacheClient.queryPassThrough(redisProperties.cache_user_pre+mindMapDTO.getUid(),
                mindMapDTO.getUid(),UserDTO.class
                ,redisProperties.session_user_save_time,TimeUnit.MINUTES
                ,this::getUser);
        if(userDTO == null){
            mindMapDTO.setName(mmProperties.default_mindmap_author);
            mindMapDTO.setAvatar(mmProperties.default_mindmap_avatar);
        }else {
            mindMapDTO.setName(userDTO.getNickname());
            mindMapDTO.setAvatar(userDTO.getAvatarUrl());
        }
        return mindMapDTO;
    }

}
